Pressidian
花园入口
笔记
项目
关于
实验室
GitHub
花园入口
笔记
项目
关于
实验室
GitHub

KNOWLEDGE PATHS

笔记库
当前位置
笔记库/前端/面试/八股/React

生命周期 ⌚️

16 分钟阅读 · Note

目录树 578 篇

            • 定时器组件 &ref与state⌚️
            • 生命周期 ⌚️
            • React
            • React 组件卸载与 this引用
          • 进阶篇题目列表
          • DOM&浏览器 API
          • HTTP&网络
          • TS
          • Vue
        • 可投递企业
      • 前端技术栈
    • 笔记目录
    • CLAUDE.md
    • Vue 组件与 Render 函数

关联笔记 6

↗定时器组件 &ref与state⌚️同一路径↗React同一路径↗React 组件卸载与 this引用同一路径↗一、ref 本身是什么共同主题↗自定义Hooks共同主题↗React Hooks 面试金句与原理共同主题
  • 生命周期 ⌚️

生命周期 ⌚️

> Last Format Time:7/30/2026 16:21:17

> 适用范围:React 16.3+ 的新生命周期模型;以 React 19.2 官方文档为准。


先说结论

  • “生命周期方法”主要是类组件的概念。函数组件没有实例,也没有与每个类生命周期一一对应的方法。
  • 函数组件的函数体属于渲染阶段;useEffect、useLayoutEffect 等 Effect 属于提交后的同步过程。Effect 应按“与外部系统同步”的思路理解,而不是机械地模拟 componentDidMount 等方法。
  • React 的一次工作可粗分为:触发更新 → 渲染(Render)→ 提交(Commit)→ 浏览器绘制(Paint)。
  • 渲染阶段必须保持纯粹。并发渲染下,React 可能暂停、重启或放弃一次渲染,所以“函数体执行了”不等于“这次结果已提交到 DOM”。
  • 类组件仍受支持,但 React 官方推荐新代码使用函数组件。

一、完整调用顺序

挂载 Mounting

constructor
  ↓
static getDerivedStateFromProps
  ↓
render
  ↓
React 更新 DOM 和 ref
  ↓
componentDidMount

前三个处于渲染阶段,componentDidMount 处于提交阶段。

更新 Updating

更新可能由 props、state 或 context 变化触发:

static getDerivedStateFromProps
  ↓
shouldComponentUpdate
  ├─ false → 跳过本组件本次 render、getSnapshotBeforeUpdate、componentDidUpdate
  └─ true
       ↓
     render
       ↓
     getSnapshotBeforeUpdate
       ↓
     React 更新 DOM 和 ref
       ↓
     componentDidUpdate

注意:shouldComponentUpdate 是性能优化提示,不适合用来保证业务逻辑正确性。调用 forceUpdate() 会跳过它。

卸载 Unmounting

componentWillUnmount
  ↓
组件从页面移除

后代渲染出错 Error Handling

后代在渲染期间抛错
  ↓
static getDerivedStateFromError
  ↓
render 降级 UI
  ↓
提交降级 UI
  ↓
componentDidCatch

二、父子组件之间的执行顺序

以下顺序以 Parent → Child → Grandchild 三层结构、未开启 Strict Mode、一次同步更新成功提交为前提。兄弟组件按 JSX 中的先后顺序逐棵深度优先处理。

必须先区分两个阶段

  • 渲染阶段:父 → 子,深度优先。 父组件先计算出子组件元素,React 才知道接下来需要渲染哪些子组件。
  • 提交阶段:不能简单概括为永远“子 → 父”。 挂载完成回调通常是子 → 父;整棵子树真正删除时,卸载清理是父 → 子;更新时还会根据 Effect 类型和节点是否被删除产生不同顺序。

渲染阶段可能被暂停、重试或放弃,提交阶段才表示这次结果真正生效。因此不能通过父子生命周期的先后顺序传递业务数据。

类组件首次挂载

Parent constructor
Parent getDerivedStateFromProps
Parent render
  Child constructor
  Child getDerivedStateFromProps
  Child render
    Grandchild constructor
    Grandchild getDerivedStateFromProps
    Grandchild render
    Grandchild componentDidMount
  Child componentDidMount
Parent componentDidMount

可记为:

  • 渲染阶段的 constructor → getDerivedStateFromProps → render:父 → 子;
  • 提交阶段的 componentDidMount:子 → 父。

若有两个兄弟 ChildA、ChildB,常见顺序是先完成 A 的整棵渲染,再完成 B 的整棵渲染;挂载提交顺序为 ChildA componentDidMount → ChildB componentDidMount → Parent componentDidMount。

类组件父子同时更新

假设父组件更新并向子组件传入新 props:

Parent getDerivedStateFromProps
Parent shouldComponentUpdate
Parent render
  Child getDerivedStateFromProps
  Child shouldComponentUpdate
  Child render
    Grandchild ...
    Grandchild getSnapshotBeforeUpdate
  Child getSnapshotBeforeUpdate
Parent getSnapshotBeforeUpdate

React 修改 DOM

    Grandchild componentDidUpdate
  Child componentDidUpdate
Parent componentDidUpdate

即:

  • 更新渲染阶段:父 → 子;
  • getSnapshotBeforeUpdate:子 → 父;
  • componentDidUpdate:子 → 父。

如果 Parent.shouldComponentUpdate() 返回 false,由这次父级更新引起的父组件后续渲染和子树遍历都会跳过;但子组件仍可能因为自己的 state、context 或其他更新源而独立更新。

类组件卸载

父组件连同整棵子树一起卸载
Parent componentWillUnmount
  Child componentWillUnmount
    Grandchild componentWillUnmount

是父 → 子,不要和挂载时的 componentDidMount 顺序混淆。

父组件更新时只移除 Child
Parent render
Parent getSnapshotBeforeUpdate   // 如果实现了
  Child componentWillUnmount
Parent componentDidUpdate

Child 的后代也会在 Child 之后继续按父 → 子执行 componentWillUnmount。

函数组件首次挂载

Parent 函数体
  Child 函数体
    Grandchild 函数体

    Grandchild useInsertionEffect setup
  Child useInsertionEffect setup
Parent useInsertionEffect setup

    Grandchild useLayoutEffect setup
  Child useLayoutEffect setup
Parent useLayoutEffect setup

浏览器通常完成绘制

    Grandchild useEffect setup
  Child useEffect setup
Parent useEffect setup

可记为:

  • 函数组件执行(渲染阶段):父 → 子;
  • useInsertionEffect、useLayoutEffect、useEffect 的首次 setup:子 → 父;
  • useLayoutEffect 在绘制前同步执行;useEffect 通常在绘制后执行。

函数组件更新

父子共同重新渲染时,函数体仍是父 → 子。对于每一个依赖发生变化的 Effect,React 保证:

该 Effect 上一轮 cleanup(旧 props/state)
  ↓
该 Effect 新一轮 setup(新 props/state)

React DOM 19.2.x 中,多层组件常见的 useEffect 更新顺序是:

Child useEffect cleanup
Parent useEffect cleanup
Child useEffect setup
Parent useEffect setup

但只应把“同一个 Effect 的 cleanup 一定先于它的下一次 setup”当作业务语义。不要让父 Effect 必须等待子 Effect,或让子 Effect 必须依赖父 Effect 已执行;React 官方没有把跨组件 Effect 的完整遍历顺序定义为组件通信契约。

useInsertionEffect 和 useLayoutEffect 在更新提交中的清理、建立时机与 useEffect 不完全相同,多个组件之间还可能交错。业务代码应依赖各自的 setup/cleanup 对称性,而不是背诵一条统一顺序。

函数组件卸载

整棵子树真正从 React 树中删除时,React DOM 19.2.x 的常见顺序是父 → 子:

Parent useLayoutEffect cleanup
  Child useLayoutEffect cleanup
    Grandchild useLayoutEffect cleanup

Parent useEffect cleanup
  Child useEffect cleanup
    Grandchild useEffect cleanup

如果父组件更新时只删除 Child,则 Child 子树的布局 Effect 在提交删除时清理,被动 useEffect 的 cleanup 随后在被动 Effect 阶段执行。

> 面试中经常出现“Effect cleanup 永远子 → 父”的错误结论。依赖变化时的重新同步与节点真正卸载是两条不同路径;最稳妥的回答是先说明场景,再给出当前 React DOM 的可观察顺序,并强调跨组件顺序不应用作业务契约。

Strict Mode 对日志顺序的影响

开发环境启用 <StrictMode> 后,组件函数、部分纯生命周期以及 Effect setup/cleanup 会额外执行检查。因此控制台可能看到重复的父子渲染,以及挂载后的 setup → cleanup → setup。生产环境没有这轮额外检查;分析基础顺序时应先关闭 Strict Mode,排除检查日志的干扰。

父子组件顺序速记

场景方向
类组件/函数组件的渲染阶段父 → 子,深度优先
componentDidMount子 → 父
getSnapshotBeforeUpdate子 → 父
componentDidUpdate子 → 父
整棵类组件子树的 componentWillUnmount父 → 子
函数组件首次 Effect setup子 → 父
同一个 Effect 依赖变化旧 cleanup → 新 setup
整棵函数组件子树真正卸载时的 Effect cleanupReact DOM 19.2.x 中通常父 → 子;不要作为跨组件通信契约

三、类组件全部生命周期 API

> render 是类组件唯一必需的方法,其余均可选。constructor 严格说是 JavaScript 构造器,但面试中通常与生命周期一起讨论。

constructor(props):实例初始化

时机: 挂载前调用。

用途

  • 初始化 state;
  • 绑定实例方法的 this。使用类字段和箭头函数后通常不再需要它。

约束

  • 其他语句之前先调用 super(props);
  • 除类字段初始化外,只在这里直接给 this.state 赋值;不能在这里调用 setState;
  • 必须纯粹,不要请求数据、订阅或操作 DOM;
  • 服务端渲染会执行 constructor 和 render,但不会执行挂载、更新、卸载相关的提交阶段生命周期。
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
}

static getDerivedStateFromProps(props, state):由 props 派生 state

时机: 初次挂载和每次更新时,在 render 之前调用。

返回值: 返回 state 更新对象,或返回 null 表示不更新。

约束

  • 必须是静态、纯函数,不能访问组件实例 this,不能执行副作用;
  • 每次父组件重新渲染时都可能调用,不只在 props 变化时调用;
  • 这是少见场景。滥用会产生“双数据源”和 state 与 props 不同步问题。

更常见的替代方案:

  • 仅计算派生值:直接在渲染期间计算,昂贵计算再考虑 useMemo;
  • props 变化后执行副作用:componentDidUpdate / useEffect;
  • props 变化时重置全部内部状态:通过变化的 key 让 React 重新挂载组件;
  • 需要保留上一轮 props:优先重新设计数据结构,确有必要再在渲染期间调整 state。
static getDerivedStateFromProps(props, state) {
  if (props.userId !== state.prevUserId) {
    return {
      prevUserId: props.userId,
      selectedItem: null,
    };
  }
  return null;
}

render():计算 UI

时机: 挂载和更新的渲染阶段。

作用: 根据 props、state、context 返回 React 节点。

约束

  • 必须是纯函数:相同输入应得到相同输出;
  • 不能订阅、请求、调用 setState 或读写 DOM;
  • React 可以多次调用或放弃某次渲染,只有提交完成才代表 UI 生效;
  • shouldComponentUpdate 返回 false 时,本次更新会跳过 render。

可返回 React 元素、字符串、数字、Portal、节点数组,以及空节点 null、undefined、true、false。

componentDidMount():挂载提交完成

时机: 组件加入屏幕后调用一次(每次实际挂载一次;不是整个应用全局只执行一次)。

用途: 建立订阅、连接外部系统、操作 DOM。数据请求也能放在这里,但现代框架的数据加载或客户端缓存方案通常更合适。

约束

  • 在这里读取了会变化的 props/state,通常还要在 componentDidUpdate 处理变化;
  • 建立的订阅、定时器、连接必须在 componentWillUnmount 对称清理;
  • 可调用 setState,但会额外渲染一次,应只用于测量 DOM 等少数场景。

shouldComponentUpdate(nextProps, nextState, nextContext):跳过不必要更新

时机: 收到新 props/state 后、渲染前;初次挂载和 forceUpdate() 时不调用。

返回值: 默认语义相当于返回 true;返回 false 会跳过本组件本次后续渲染流程。

约束

  • 必须纯粹,不能产生副作用或调用 setState;
  • 不建议手写深比较或 JSON.stringify,可能比渲染更慢;
  • 类组件通常优先继承 PureComponent;函数组件对应 memo;
  • 不能把返回 false 当成阻止渲染的业务保证,React 官方只把它定位为性能优化。

PureComponent 会对 props 和 state 的每个字段做浅比较。因此不可直接修改原对象后继续复用同一引用。

getSnapshotBeforeUpdate(prevProps, prevState):提交 DOM 前取快照

时机: render 之后、React 修改 DOM 之前。

返回值: 任意快照值或 null;该值会成为 componentDidUpdate 的第三个参数。

用途: 在 DOM 改变前读取滚动位置等信息,再在 DOM 改变后恢复。

约束

  • shouldComponentUpdate 返回 false 时不会调用;
  • 函数组件目前没有精确等价 Hook;
  • 不要在 render 或 UNSAFE_componentWillUpdate 中读取这类 DOM 快照,因为并发渲染下渲染与提交之间可能存在时间间隔。
getSnapshotBeforeUpdate(prevProps) {
  if (prevProps.items.length < this.props.items.length) {
    const list = this.listRef.current;
    return list.scrollHeight - list.scrollTop;
  }
  return null;
}

componentDidUpdate(prevProps, prevState, snapshot) {
  if (snapshot !== null) {
    const list = this.listRef.current;
    list.scrollTop = list.scrollHeight - snapshot;
  }
}

componentDidUpdate(prevProps, prevState, snapshot):更新提交完成

时机: 更新后的 DOM 已提交时调用;初次挂载不调用。

参数

  • prevProps:更新前 props;
  • prevState:更新前 state;
  • snapshot:getSnapshotBeforeUpdate 的返回值,未实现该方法时为 undefined。

约束

  • shouldComponentUpdate 返回 false 时不会调用;
  • 调用 setState 前必须做前后值判断,否则容易无限更新;
  • 常与 componentDidMount、componentWillUnmount 组成一套对称的同步逻辑。
componentDidUpdate(prevProps) {
  if (prevProps.roomId !== this.props.roomId) {
    this.disconnect(prevProps.roomId);
    this.connect(this.props.roomId);
  }
}

componentWillUnmount():卸载前清理

时机: 组件从屏幕移除前调用。

用途: 取消订阅、清除定时器、断开连接、取消仍可取消的请求等。

约束

  • 清理逻辑应与 componentDidMount 的建立逻辑对称;
  • 不应调用 setState,组件不会再次渲染;
  • 不能假设它会在浏览器崩溃、进程结束等非正常退出场景执行。

static getDerivedStateFromError(error):错误降级 UI

时机: 后代组件在渲染期间抛错后调用。

返回值: state 更新对象,用于下一次渲染显示降级 UI。

约束: 必须纯粹,不在这里上报错误;日志上报放到 componentDidCatch。

componentDidCatch(error, info):记录后代错误

时机: 后代渲染错误被捕获、降级 UI 提交后调用。

参数: error 是抛出的值;info.componentStack 是组件栈。

定义 getDerivedStateFromError 和/或 componentDidCatch 的组件称为 Error Boundary(错误边界)。

错误边界能捕获后代在以下位置抛出的错误:

  • 渲染期间;
  • 构造器;
  • 生命周期方法。

它通常不能捕获:

  • 自身内部抛出的错误;
  • 事件处理函数中的错误(应用普通 try...catch);
  • setTimeout 等异步回调中的错误;
  • 服务端渲染中的错误。

函数组件目前没有内置的、与 componentDidCatch 完全等价的 Hook,通常保留一个类错误边界或使用封装库。

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    reportError(error, info.componentStack);
  }

  render() {
    return this.state.hasError
      ? <p>页面出现异常</p>
      : this.props.children;
  }
}

四、不安全/已废弃生命周期

以下旧名称已经废弃:

  • componentWillMount → UNSAFE_componentWillMount
  • componentWillReceiveProps → UNSAFE_componentWillReceiveProps
  • componentWillUpdate → UNSAFE_componentWillUpdate

它们在异步/并发渲染中不安全:渲染工作可能被重复、暂停或放弃,因此不能依赖它们“只执行一次”,也不能在其中执行副作用。新代码不要使用。

UNSAFE_componentWillMount()

  • 挂载前调用,历史上位于 constructor 与 render 之间;
  • 初始化 state 放到字段或 constructor;副作用放到 componentDidMount;
  • 服务端渲染时它是除 constructor、render 外还会执行的旧生命周期,因此更容易导致服务端/客户端行为不一致。

UNSAFE_componentWillReceiveProps(nextProps, nextContext)

  • 已挂载组件接收父级新 props 时调用;初次挂载不调用;
  • 父组件重新渲染即可触发,不能以“值一定改变”为前提;
  • 根据 props 计算数据应在渲染期间完成;需要重置 state 时优先使用受控组件或 key。

UNSAFE_componentWillUpdate(nextProps, nextState)

  • 更新渲染前调用,初次挂载不调用;
  • 不能调用 setState;不能可靠地在此读取更新前 DOM;
  • DOM 快照应使用 getSnapshotBeforeUpdate,提交后的副作用使用 componentDidUpdate。

如果类中实现了 static getDerivedStateFromProps 或 getSnapshotBeforeUpdate,React 不会调用这些 UNSAFE_ 旧生命周期。


五、函数组件如何正确理解“生命周期”

函数组件函数体:对应渲染阶段,不等于 render() 的一次提交

function Profile({ user }) {
  const [tab, setTab] = useState('posts');
  const title = `${user.name} - ${tab}`;
  return <h1>{title}</h1>;
}

函数体应保持纯粹。不要在其中请求、订阅、写 DOM 或注册定时器。

初始化状态:useState / useReducer

函数组件没有 constructor 的精确对应物。昂贵的初始计算使用惰性初始化函数:

const [state, setState] = useState(() => createInitialState(props));

初始化函数也必须纯粹;Strict Mode 开发环境可能调用两次来检查纯度。

useEffect:与外部系统同步

useEffect(() => {
  const connection = createConnection(serverUrl, roomId);
  connection.connect();

  return () => connection.disconnect();
}, [serverUrl, roomId]);

对于很多场景,一个 Effect 的 setup/cleanup 可以覆盖类组件中分散在以下三处的逻辑:

  • componentDidMount:建立连接;
  • componentDidUpdate:依赖改变时先清理旧连接,再建立新连接;
  • componentWillUnmount:清理最后的连接。

但它们并不完全等价:

  • useEffect 在初次提交后也会执行,不能直接表示“只在更新后、不在挂载后”;
  • 依赖变化时,React 会先以旧值执行 cleanup,再以新值执行 setup;
  • 卸载时执行最后一次 cleanup;
  • useEffect 通常允许浏览器先绘制,视觉测量/同步布局应考虑 useLayoutEffect;
  • Effect 只在客户端执行,服务端渲染不执行。

useLayoutEffect:绘制前同步布局

执行在 DOM 提交后、浏览器重新绘制前,适合测量 DOM 并立刻修正布局。它会阻塞绘制,应优先使用 useEffect,仅在避免闪烁或同步测量时使用。

useLayoutEffect(() => {
  const rect = ref.current.getBoundingClientRect();
  setHeight(rect.height);
}, []);

它比 useEffect 更接近类组件 componentDidMount / componentDidUpdate 的提交时机,但仍不是 getSnapshotBeforeUpdate 的替代品。

useInsertionEffect:CSS-in-JS 库专用

它会在其他布局 Effect 运行前插入动态样式。普通业务组件不应使用;其主要受众是运行时 CSS-in-JS 库作者。此时 ref 还未附加,也不能在其中更新 state。

性能优化:memo、useMemo、useCallback

类组件函数组件准确含义
PureComponent / shouldComponentUpdatememo在 props 未变化时尝试跳过组件重新渲染
在实例字段中缓存结果useMemo缓存一次计算结果
稳定的实例方法useCallback缓存函数引用

它们都是性能优化工具,不是保证语义正确的生命周期机制。useMemo、useCallback 也不能用来承载副作用。

没有精确 Hook 对应物的生命周期

类 API函数组件结论
getSnapshotBeforeUpdate目前没有精确等价 Hook,必要时保留类组件
componentDidCatch / 错误边界目前没有内置函数组件错误边界 Hook,使用类边界或封装库
getDerivedStateFromProps通常在渲染期间直接计算、使用 key 重置状态或重新设计状态结构
仅更新时运行的 componentDidUpdateuseEffect 默认挂载后也运行;可用 ref 跳过首轮,但先确认需求是否合理

六、Effect 依赖项完整规则

写法setup 执行时机cleanup 执行时机
省略第二个参数每次提交后下一次 setup 前;卸载时
空数组 []该次挂载提交后卸载时
[dep1, dep2]挂载提交后;任一依赖与上次不同时依赖变化后的新 setup 前;卸载时

更严谨地说,[] 表示“这个 Effect 没有响应式依赖”,不是“整个应用生命周期中绝对只执行一次”。组件因 key 变化、条件渲染或路由切换而重新挂载时会再次执行;Strict Mode 开发环境还会额外执行一次 setup → cleanup → setup 检查。

依赖是如何比较的

  • React 使用 Object.is 逐项比较新旧依赖,不是递归深比较;
  • 依赖列表必须写成固定长度的内联数组,如 [a, b];
  • props、state,以及组件函数体内声明并被 Effect 读取的变量和函数,都属于响应式值,应列入依赖;
  • 不要为了控制执行次数故意漏依赖,应启用 eslint-plugin-react-hooks 的 exhaustive-deps 规则。

对象和函数依赖

每次渲染新建的对象、数组、函数具有新引用,Object.is 比较会判定其变化。优先按以下顺序处理:

  1. 删除本来就不需要的 Effect;
  2. 把对象或函数移到 Effect 内部创建;
  3. 依赖真正使用的基础类型字段;
  4. 确有共享稳定引用的需要时,再用 useMemo / useCallback。

不要为了“让依赖稳定”而无条件缓存一切。

闭包与旧值

每次渲染都有自己的 props、state 和闭包。Effect 捕获的是创建它的那次渲染中的值:

// 错误:count 被固定为首次渲染的值
useEffect(() => {
  const id = setInterval(() => setCount(count + 1), 1000);
  return () => clearInterval(id);
}, []);

// 正确:函数式更新不读取外部 count
useEffect(() => {
  const id = setInterval(() => setCount(c => c + 1), 1000);
  return () => clearInterval(id);
}, []);

需要读取最新值但不希望它触发 Effect 重新同步时,应先判断这段逻辑是否属于非响应式事件;可使用 ref,或在支持的 React 版本中使用 useEffectEvent。不能把这当作随意绕过依赖规则的手段。


七、Strict Mode 下为什么像“执行了两次”

&lt;StrictMode&gt; 只在开发环境增加检查,不影响生产构建。常见行为包括:

  • 额外调用组件函数、类 constructor、render、shouldComponentUpdate 等应保持纯粹的逻辑,以发现副作用;
  • 初次挂载时额外执行一次 Effect 的 setup → cleanup,再执行真实 setup;
  • 类组件会出现 componentDidMount → componentWillUnmount → componentDidMount 的检查序列;
  • ref callback 也会额外执行 setup/cleanup 检查。

正确修复方式是让渲染保持纯粹、让 cleanup 完整撤销 setup,而不是用全局标记阻止第二次执行。


八、面试速记表

阶段类组件 API能否产生副作用函数组件常见方案
挂载前constructor否useState / useReducer 惰性初始化
挂载/更新渲染前getDerivedStateFromProps否渲染期间派生;必要时 key 重置
渲染render否函数组件函数体
更新判断shouldComponentUpdate否memo
DOM 更新前快照getSnapshotBeforeUpdate只读 DOM无精确 Hook 对应物
挂载提交后componentDidMount可以useEffect / useLayoutEffect
更新提交后componentDidUpdate可以依赖明确的 useEffect / useLayoutEffect
卸载前componentWillUnmount只做清理Effect cleanup
错误降级getDerivedStateFromError否无内置函数边界 Hook
错误记录componentDidCatch可以类错误边界或封装库
遗留、不安全三个 UNSAFE_ 方法不要使用迁移到现行 API

九、常见追问

useEffect(fn, []) 是否等于 componentDidMount?

不严格相等。它表示无响应式依赖的 Effect:客户端挂载提交后执行 setup,卸载时 cleanup;重新挂载会再次执行,Strict Mode 开发环境还会多一次检查循环。

cleanup 是否只在卸载时执行?

不是。依赖变化时,新 setup 执行前会先运行上一轮 cleanup;卸载时再运行最后一轮 cleanup。

useLayoutEffect 与 useEffect 的核心区别?

useLayoutEffect 在浏览器绘制前同步执行并阻塞绘制;useEffect 通常允许浏览器先绘制。默认用 useEffect,需要测量并同步修正布局以避免闪烁时才用 useLayoutEffect。

React 生命周期的本质是什么?

类组件按组件实例的挂载、更新、卸载组织逻辑;Effect 则按“建立某个外部同步过程,以及何时清理/重建它”组织逻辑。Hooks 的重点是按关注点聚合 setup 和 cleanup,而不是逐个复刻类生命周期。


官方资料

  • Component(完整类组件 API)
  • useEffect
  • useLayoutEffect
  • useInsertionEffect
  • StrictMode
  • Components and Hooks must be pure